package com.wctj.api.security.filter;

import com.wctj.api.base.Result;
import com.wctj.api.base.constant.Constants;
import com.wctj.api.base.constant.JwtConstant;
import com.wctj.api.base.enums.ApiCode;
import com.wctj.api.base.utils.JsonUtils;
import com.wctj.api.base.utils.JwtValidator;
import com.wctj.api.base.utils.RedisUtil;
import com.wctj.api.security.config.IgnoreUrlsConfig;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Objects;

/**
 * 统一认证拦截
 *
 * @author lzg
 * @date 2024-10-22 18:46
 */
@Component
@Slf4j
public class AuthFilter implements Filter, Ordered {

    /**
     * 不进行token校验的请求地址
     */
    @Resource
    private IgnoreUrlsConfig ignoreUrlsConfig;
    @Resource
    private JwtValidator validator;
    @Resource
    private RedisUtil redisUtil;
    private final AntPathMatcher antPathMatcher;


    public AuthFilter() {
        this.antPathMatcher = new AntPathMatcher();
        antPathMatcher.setCaseSensitive(true);
        antPathMatcher.setCachePatterns(true);
    }


    private boolean skipValidation(HttpServletRequest exchange) {
        String path = exchange.getRequestURI();
        if (ignoreUrlsConfig.getIgnoreUrls().contains(path)) {
            return true;
        } else {
            return ignoreUrlsConfig.getIgnoreUrls().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
        }
    }



    /**
     * 拦截所有的请求头
     *
     * @return
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest exchange = (HttpServletRequest) request;

        // openapi/** 开头
        if (skipValidation(exchange) || !ignoreUrlsConfig.getEnabled()) {
            // request.getRequestURI()：返回请求的URI部分，例如 /test/path?param=value 中的 /test/path
            log.debug("url<{}>无需过滤", exchange.getRequestURI());
            chain.doFilter(request,response);
            return;
        }

        this.jwtFilter(request,response, chain);
    }

    private void jwtFilter(ServletRequest exchange, ServletResponse servletResponse, FilterChain chain) {
        HttpServletRequest request = (HttpServletRequest) exchange;
        String token = request.getHeader(JwtConstant.HEADER_TOKEN_CODE);
        if (StringUtils.isBlank(token)) {
            token = request.getParameter(JwtConstant.HEADER_TOKEN_CODE);
        }
        if (token != null) {
            token = token.replaceFirst("(?i)Bearer ", "");
        }
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (StringUtils.isBlank(token)) {
            writeResponse(response, ApiCode.NO_VALIDATION_TOKEN, HttpStatus.UNAUTHORIZED);
            return ;
        }
        try {
            Claims claims = validator.getClaimsFromToken(token);
            String username = claims.getSubject();
            if (claims.getExpiration().before(new Date())) {
                log.warn("jwt信息已过期，username={}", username);
                writeResponse(response, ApiCode.NO_VALIDATION_TOKEN, HttpStatus.UNAUTHORIZED);
                return;
            }
            // 判断缓存中是否存在
            String cacheToken = (String) redisUtil.get(Constants.LOGIN_TOKEN + claims.get(JwtConstant.CLAIMS_NAME_USER_ID));
            if(!token.equals(cacheToken)){
                log.warn("jwt信息已过期或已注销，username={},cacheToken", username,cacheToken);
                writeResponse(response, ApiCode.VALIDATION_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED);
                return;
            }
            HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(request);
            requestWrapper.addHeader(JwtConstant.HEADER_NAME_GROUP_ID, claims.get(JwtConstant.CLAIMS_NAME_GROUP_ID));
            requestWrapper.addHeader(JwtConstant.HEADER_NAME_SYSTEM_CODE, claims.get(JwtConstant.CLAIMS_NAME_SYSTEM_CODE));
            requestWrapper.addHeader(JwtConstant.HEADER_NAME_USERNAME, claims.getSubject());
            requestWrapper.addHeader(JwtConstant.HEADER_NAME_USER_ID, claims.get(JwtConstant.CLAIMS_NAME_USER_ID));
            requestWrapper.addHeader(JwtConstant.HEADER_NAME_IS_ADMIN, claims.get(JwtConstant.CLAIMS_NAME_IS_ADMIN));

            requestWrapper.addHeader(JwtConstant.HEADER_NAME_AID, claims.get(JwtConstant.CLAIMS_NAME_AID));
            requestWrapper.addHeader(JwtConstant.HEADER_NAME_BID, claims.get(JwtConstant.CLAIMS_NAME_BID));
            requestWrapper.addHeader(JwtConstant.HEADER_NAME_VAR1, claims.get(JwtConstant.CLAIMS_NAME_VAR1));
            requestWrapper.addHeader(JwtConstant.HEADER_NAME_VAR2, claims.get(JwtConstant.CLAIMS_NAME_VAR2));
            requestWrapper.addHeader(JwtConstant.HEADER_NAME_VAR3, claims.get(JwtConstant.CLAIMS_NAME_VAR3));

            log.debug("请求{}成功通过jwt认证", request.getRequestURI());
            chain.doFilter(requestWrapper,response);
        } catch (Exception e) {
            log.warn("解析jwt认证信息失败,token={}", token, e);
            writeResponse(response, ApiCode.VALIDATION_FAILED, HttpStatus.UNAUTHORIZED);
        }
    }


    /**
     * 认证失败信息输出
     *
     * @param response     响应对象
     * @param status       http状态码
     * @return
     */
    private void writeResponse(HttpServletResponse response, ApiCode code, HttpStatus status) {
        if (Objects.nonNull(status)) {
            response.setStatus(status.value());
        }
        response.setHeader(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
        Result result = new Result(code);
        response.setCharacterEncoding(Constants.CHARSET_UTF8);

        try (PrintWriter pw = response.getWriter()) {
            pw.write(JsonUtils.toJson(result));
            pw.flush();
        } catch (IOException e) {
            log.warn("writeResponse" + e.getMessage(), e);
        }
    }


    @Override
    public int getOrder() {
        return -100;
    }


}